<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>渲染函数 & JSX</title>
    <script src="../vue.js"></script>
  </head>
  <body>
    <!-- 1 基础 -->
    <!-- 让我们深入一个简单的例子，这个例子里 render 函数很实用。假设我们要生成一些带锚点的标题：
    
    <h1>
      <a name="hello-world" href="#hello-world">
        Hello world!
      </a>
    </h1> -->
    <div id="app1" style="border: 1px dashed red;margin-bottom: 2px;">
      <anchored-heading :level="level">Hello world!</anchored-heading>
    </div>
    <div id="app2" style="border: 1px dashed blue;margin-bottom: 2px;">
      <anchored-heading2 :level="level">Hello world!2</anchored-heading2>
    </div>
    <div id="app3" style="border: 1px dashed red;margin-bottom: 2px;">
      <anchored-heading3 :level="level">Hello world!3</anchored-heading3>
    </div>
    <!-- 2 节点、树以及虚拟 DOM -->
    <!-- 2.1 虚拟 DOM -->
    <!-- 3 createElement 参数 -->
    <!-- 3.1 深入数据对象 -->
    <!-- 3.2 完整示例 -->
    <!-- 3.3 约束 -->
    <!-- 4 使用 JavaScript 代替模板功能 -->
    <!-- 4.1 v-if 和 v-for -->
    <div id="app4" style="border: 1px dashed blue;margin-bottom: 2px;">
      <cmt4 :items="items"></cmt4>
    </div>
    <!-- 4.2 v-model -->
    <div id="app5" style="border: 1px dashed red;margin-bottom: 2px;">
      <cmt5 v-model="msg"></cmt5>
      <br />
      {{msg}}
    </div>
    <!-- 4.3 事件 & 按键修饰符 -->
    <!-- 4.4 插槽 -->
    <div id="app7" style="border: 1px dashed blue;margin-bottom: 2px;">
      <cmt7 :msg="msg">
        <template v-slot:default="slotProps">
          {{slotProps.text}}
        </template>
      </cmt7>
      ------
      <cmt7 :msg="msg">
        <template v-slot="slotProps">
          {{slotProps.text}}
        </template>
      </cmt7>
      ------
      <cmt7 :msg="msg">
        <template #default="slotProps">
          {{slotProps.text}}
        </template>
      </cmt7>
      ------
      <cmt7 :msg="msg" v-slot:default="slotProps">
          {{slotProps.text}}
      </cmt7>
      ------
      <cmt7 :msg="msg" v-slot="slotProps">
          {{slotProps.text}}
      </cmt7>
      ------
      <cmt7 :msg="msg" #default="slotProps">
          {{slotProps.text}}
      </cmt7>
      ---解构插槽---
      <cmt7 :msg="msg" #default="{text}">
          {{text}}
      </cmt7>
      ---解构插槽，起别名---
      <cmt7 :msg="msg" #default="{text:text2}">
          {{text2}}
      </cmt7>
    </div>
    <div id="app8" style="border: 1px dashed red;margin-bottom: 2px;">
      ？？？
      <cmt8 :text="text" v-slot="props">
        {{props.text}}
      </cmt8>
    </div>
    <!-- 5 JSX -->
    <div id="app9" style="border: 1px dashed blue;margin-bottom: 2px;">
      <cmt9>
      </cmt9>
    </div>
    <div id="app10" style="border: 1px dashed red;margin-bottom: 2px;">
    </div>
    <!-- 6 函数式组件 -->
    <div id="app11" style="border: 1px dashed blue;margin-bottom: 2px;">
      <smart-list :items="items" :is-ordered="isOrdered" attr1="a001"></smart-list>
    </div>
    <!-- 6.1 向子元素或子组件传递 attribute 和事件 -->
    <!-- 6.2 slots() 和 children 对比 -->
    <!-- 7 模板编译 -->
    
   <script type="text/x-template" id="anchored-heading-template">
      <h1 v-if="level === 1">
        <slot></slot>
      </h1>
      <h2 v-else-if="level === 2">
        <slot></slot>
      </h2>
      <h3 v-else-if="level === 3">
        <slot></slot>
      </h3>
      <h4 v-else-if="level === 4">
        <slot></slot>
      </h4>
      <h5 v-else-if="level === 5">
        <slot></slot>
      </h5>
      <h6 v-else-if="level === 6">
        <slot></slot>
      </h6>
    </script>
    <script type="text/javascript">
      // 1 基础
      Vue.component('anchored-heading', {
        template: '#anchored-heading-template',
        props: {
          level: Number,
          required: true
        }
      })
      new Vue({
        el: '#app1',
        data: {
          level: 3
        }
      })
      /* 这里用模板并不是最好的选择：不但代码冗长，而且在每一个级别的标题中重复书写了 <slot></slot>，
      在要插入锚点元素时还要再次重复。
      虽然模板在大多数组件中都非常好用，但是显然在这里它就不合适了。那么，我们来尝试使用 render 函数重写上面的例子： */
      Vue.component('anchored-heading2', {
        render: function(createElement) {
          return createElement(
            'h' + this.level, // 标签名称
            this.$slots.default // 子节点数组
          )
        },
        props: {
          level: {
            type: Number,
            required: true
          }
        }
      })
      new Vue({
        el: '#app2',
        data: {
          level: 5
        }
      })
      /* 看起来简单多了！这样代码精简很多，但是需要非常熟悉 Vue 的实例 property。
      在这个例子中，你需要知道，向组件中传递不带 v-slot 指令的子节点时，比如 anchored-heading 中的 Hello world!，
      这些子节点被存储在组件实例中的 $slots.default 中。如果你还不了解，在深入渲染函数之前推荐阅读实例 property API。 */
      
      // 2 节点、树以及虚拟 DOM
      // 2.1 虚拟 DOM
      // 3 createElement 参数
      /* 
      // @returns {VNode}
      createElement(
        // {String | Object | Function}
        // 一个 HTML 标签名、组件选项对象，或者
        // resolve 了上述任何一种的一个 async 函数。必填项。
        'div',
      
        // {Object}
        // 一个与模板中 attribute 对应的数据对象。可选。
        {
          // (详情见下一节)
        },
      
        // {String | Array}
        // 子级虚拟节点 (VNodes)，由 `createElement()` 构建而成，
        // 也可以使用字符串来生成“文本虚拟节点”。可选。
        [
          '先写一些文字',
          createElement('h1', '一则头条'),
          createElement(MyComponent, {
            props: {
              someProp: 'foobar'
            }
          })
        ]
      )
      */
     
      // 3.1 深入数据对象
      // 3.2 完整示例
      //   有了以上的知识，我们现在可以完成我们最开始想实现的组件：
      var getChildrenTextContent = function(children) {
        return children.map(function(node) {
          return node.children ? getChildrenTextContent(node.children) : node.text
        }).join('')
      }
      
      Vue.component('anchored-heading3',{
        render: function(createElement) {
          // 创建 kebab-case 风格的 ID
          var headingId = getChildrenTextContent(this.$slots.default)
            .toLowerCase()
            .replace(/\W+/g, '-')
            .replace(/(^-|-$)/g, '')
          
          return createElement(
            'h' + this.level,
            [
              createElement('a', {
                attrs: {
                  name: headingId,
                  href: '#' + headingId
                }
              }, this.$slots.default)
            ]
          )
        },
        props: {
          level: {
            type: Number,
            required: true
          }
        }
      })
       
        new Vue({
          el: '#app3',
          data: {
            level: 2
          }
        })
      
      // 3.3 约束
      // 4 使用 JavaScript 代替模板功能
      // 4.1 v-if 和 v-for
      /* 
      只要在原生的 JavaScript 中可以轻松完成的操作，Vue 的渲染函数就不会提供专有的替代方法。
      比如，在模板中使用的 v-if 和 v-for：
      
      <ul v-if="items.length">
        <li v-for="item in items">{{ item.name }}</li>
      </ul>
      <p v-else>No items found.</p>
      
      这些都可以在渲染函数中用 JavaScript 的 if/else 和 map 来重写：
      */
     Vue.component('cmt4',{
       props: ['items'],
       render: function(createElement) {
         if(this.items.length) {
           return createElement('ul', this.items.map(function(item) {
             return createElement('li', item.name)
           }))
         } else {
           return createElement('p', 'No items found.')
         }
       }
     })
     
     var vm4 = new Vue({
       el: '#app4',
       data: {
         items: [
           {id: 1, name: 'item1'},
           {id: 2, name: 'item2'},
           {id: 3, name: 'item3'}
         ]
       }
     })
      // 4.2 v-model
      //   渲染函数中没有与 v-model 的直接对应——你必须自己实现相应的逻辑：
      Vue.component('cmt5', {
        props: ['value'],
        render: function(createElement) {
          var self = this
          return createElement('input', {
            domProps: {
              value: self.value
            },
            on: {
              input: function(event) {
                self.$emit('input', event.target.value)
              }
            }
          })
        }
      })
      var vm5 = new Vue({
        el: '#app5',
        data: {
          msg: 'binding message: '
        }
      })

      // 4.3 事件 & 按键修饰符
      // 4.4 插槽
      //   也可以通过 this.$scopedSlots 访问作用域插槽，每个作用域插槽都是一个返回若干 VNode 的函数：
      Vue.component('cmt7', {
        props: ['msg'],
        render: function(createElement) {
          // <div><slot :text="msg"></slot></div>
          return createElement('div', [
            this.$scopedSlots.default({
              text: this.msg
            })
          ])
        }
      })
      var vm7 = new Vue({
        el: '#app7',
        data: {
          msg: 'hello scoped slots!'
        }
      })
      
      //   如果要用渲染函数向子组件中传递作用域插槽，可以利用 VNode 数据对象中的 scopedSlots 字段：？？？
      Vue.component('child', {
        props: ['text'],
        template: '<div></div>'
      })
      Vue.component('cmt8', {
        props: ['text'],
        render: function(createElement) {
          // `<div><child v-slot="props"><span>{{ props.text }}</span></child></div>`
          return createElement('div', [
            createElement('child', {
              // 在数据对象中传递 `scopedSlots`
              // 格式为 { name: props => VNode | Array<VNode> }
              scopedSlots: {
                default: function (props) {
                  return createElement('span', props.text)
                }
              }
            })
          ])
        }
      })
      new Vue({
        el: '#app8',
        data: {
          text: 'scoped slots - text.'
        }
      })
      // 5 JSX
      /* 如果你写了很多 render 函数，可能会觉得下面这样的代码写起来很痛苦：
      特别是对应的模板如此简单的情况下：

      <anchored-heading :level="1">
        <span>Hello</span> world!
      </anchored-heading> */
      Vue.component('cmt9', {
        render: function(createElement) {
          return createElement(
            'anchored-heading2', {
              props: {
                level: 3
              }
            }, [
              createElement('span', 'Hello'),
              ' world!'
            ]
          )
        }
      })
      
      new Vue({
        el: '#app9'
      })
      //   这就是为什么会有一个 Babel 插件，用于在 Vue 中使用 JSX 语法，它可以让我们回到更接近于模板的语法上
      /* new Vue({
        el: '#app10',
        render: function (h) {
          return (
            <anchored-heading2 level={1}>
              <span>Hello</span> world!
            </anchored-heading2>
          )
        }
      }) */
      
      // 6 函数式组件
      var EmptyList = {
        template: '<div>empty list</div>'
      }
      var TableList = {
        template: '<div>table list</div>'
      }
      var OrderedList = {template: '<div>ordered list</div>'}
      var UnorderedList = {template: '<div>unordered list</div>'}

      Vue.component('smart-list', {
        functional: true,
        props: {
          items: {
            type: Array,
            required: true
          },
          isOrdered: Boolean
        },
        render: function(createElement, context) {
          function appropriateListComponent() {
            var items = context.props.items
            console.log(items)
            console.log(context.props.isOrdered)
            console.log(context.data.attrs)
            // if (items.length === 0) return EmptyList
            // if (typeof items[0] === 'object') return TableList
            if (context.props.isOrdered) return OrderedList
            // if (context.data.attrs['is-ordered']) return OrderedList
            return UnorderedList
          }
          
          return createElement(
            appropriateListComponent(),
            context.data,
            context.children
          )
        }
      })
      new Vue({
        el: '#app11',
        data: {
          items: [
            {id: 1, text: 'aaa'},
            {id: 2, text: 'bbb'},
            {id: 3, text: 'ccc'}
          ],
          isOrdered: true
        }
      })
      // 6.1 向子元素或子组件传递 attribute 和事件
      // 6.2 slots() 和 children 对比
      // 7 模板编译
    </script>
  </body>
</html>
